/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2022. All rights reserved.
 *
 * File Name     : roce_xrc.c
 * Version       : v2.0
 * Created       : 2021/3/12
 * Last Modified : 2021/12/7
 * Description   : The definition of functions for RoCE XRC features.
 */

#include <linux/slab.h>

#include "roce.h"
#include "roce_main_extension.h"
#include "roce_xrc.h"

static int roce3_rdma_xrcd_alloc(void *hwdev, u32 *xrcdn)
{
    struct rdma_comp_priv *comp_priv = NULL;

    if ((hwdev == NULL) || (xrcdn == NULL)) {
        pr_err("%s: Hwdev or xrcdn is null\n", __FUNCTION__);
        return -EINVAL;
    }

    comp_priv = get_rdma_comp_priv(hwdev);
    if (comp_priv == NULL) {
        pr_err("%s: Comp_priv is null\n", __FUNCTION__);
        return -EINVAL;
    }

    *xrcdn = rdma_bitmap_alloc(&comp_priv->xrcd_bitmap);
    if (*xrcdn == RDMA_INVALID_INDEX) {
        pr_err("%s: Can't get valid xrcdn, err(%d)\n", __FUNCTION__, -ENOMEM);
        return -ENOMEM;
    }

    return 0;
}

static void roce3_rdma_xrcd_free(void *hwdev, u32 xrcdn)
{
    struct rdma_comp_priv *comp_priv = NULL;

    if (hwdev == NULL) {
        pr_err("%s: Hwdev is null\n", __FUNCTION__);
        return;
    }

    comp_priv = get_rdma_comp_priv(hwdev);
    if (comp_priv == NULL) {
        pr_err("%s: Comp_priv is null\n", __FUNCTION__);
        return;
    }

    rdma_bitmap_free(&comp_priv->xrcd_bitmap, xrcdn);
}

static int roce3_init_xrcd(struct ib_device *ibdev, struct roce3_device *rdev, struct roce3_xrcd *xrcd)
{
    int ret = 0;
    struct ib_cq_init_attr cq_attr = { 0 };

    ret = roce3_rdma_xrcd_alloc(rdev->hwdev, &xrcd->xrcdn);
    if (ret != 0) {
        dev_err(rdev->hwdev_hdl, "[ROCE, ERR] %s: Failed to alloc rdma xrcd, func_id(%d)\n", __func__,
            rdev->glb_func_id);
        goto err_alloc_xrcdn;
    }

    xrcd->pd = ib_alloc_pd(ibdev, 0); /*lint !e119*/

    if (IS_ERR(xrcd->pd)) {
        ret = (int)PTR_ERR(xrcd->pd);
        dev_err(rdev->hwdev_hdl, "[ROCE, ERR] %s: Failed to alloc pd, func_id(%d)\n", __func__, rdev->glb_func_id);
        goto err_alloc_pd;
    }

    cq_attr.cqe = 1;
    xrcd->cq = ib_create_cq(ibdev, NULL, NULL, xrcd, &cq_attr);
    if (IS_ERR(xrcd->cq)) {
        ret = (int)PTR_ERR(xrcd->cq);
        dev_err(rdev->hwdev_hdl, "[ROCE, ERR] %s: Failed to create cq, func_id(%d)\n", __func__, rdev->glb_func_id);
        goto err_create_cq;
    }
    return 0;

err_create_cq:
    ib_dealloc_pd(xrcd->pd);

err_alloc_pd:
    roce3_rdma_xrcd_free(rdev->hwdev, xrcd->xrcdn);

err_alloc_xrcdn:
    return ret;
}

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) || defined(OFED_MLNX_5_7_8) || defined(OFED_MLNX_5_4)
int roce3_alloc_xrcd(struct ib_xrcd *ibxrcd, struct ib_udata *udata)
{
    struct roce3_device *rdev = to_roce3_dev(ibxrcd->device);
    struct roce3_xrcd *xrcd = to_roce3_xrcd(ibxrcd);

    if (roce3_hca_is_present(rdev) == 0) {
        dev_err(rdev->hwdev_hdl, "[ROCE] %s: HCA not present(return fail), func_id(%hu)\n",
            __func__, rdev->glb_func_id);
        return -EPERM;
    }

    return roce3_init_xrcd(ibxrcd->device, rdev, xrcd);
}
#else
#if defined(CENTOS8_2_4_18_0)
struct ib_xrcd *roce3_alloc_xrcd(struct ib_device *ibdev, struct ib_udata *udata)
#else
struct ib_xrcd *roce3_alloc_xrcd(struct ib_device *ibdev, struct ib_ucontext *ibcontext, struct ib_udata *udata)
#endif
{
    int ret = 0;
    struct roce3_device *rdev = NULL;
    struct roce3_xrcd *xrcd = NULL;

    if (ibdev == NULL) {
        pr_err("[ROCE, ERR] %s: Ibdev is null\n", __func__);
        ret = (-EINVAL);
        goto err_out;
    }

    rdev = to_roce3_dev(ibdev);
    if (roce3_hca_is_present(rdev) == 0) {
        dev_err(rdev->hwdev_hdl, "[ROCE] %s: HCA not present(return fail), func_id(%hu)\n",
            __func__, rdev->glb_func_id);
        ret = (-EPERM);
        goto err_out;
    }

    xrcd = (struct roce3_xrcd *)kzalloc(sizeof(*xrcd), GFP_KERNEL);
    if (xrcd == NULL) {
        ret = -ENOMEM;
        dev_err(rdev->hwdev_hdl, "[ROCE, ERR] %s: Failed to alloc memory for xrcd, func_id(%hu)\n", __func__,
            rdev->glb_func_id);
        goto err_out;
    }

    ret = roce3_init_xrcd(ibdev, rdev, xrcd);
    if (ret != 0) {
        dev_err(rdev->hwdev_hdl, "[ROCE, ERR] %s: Failed to init xrcd, func_id(%hu)\n", __func__, rdev->glb_func_id);
        goto err_init_xrcd;
    }
    return &xrcd->ibxrcd;

err_init_xrcd:
    kfree(xrcd);

err_out:
    return (struct ib_xrcd *)ERR_PTR((long)ret);
}

#endif

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)) || defined(OFED_MLNX_5_7_8) || defined(CENTOS8_2_4_18_0)
int roce3_dealloc_xrcd(struct ib_xrcd *ibxrcd, struct ib_udata *udata)
{
    struct roce3_device *rdev = NULL;
    struct roce3_xrcd *xrcd = NULL;

    if (ibxrcd == NULL) {
        pr_err("[ROCE, ERR] %s: Ibxrcd is null\n", __func__);
        return -EINVAL;
    }

    rdev = to_roce3_dev(ibxrcd->device);
    xrcd = to_roce3_xrcd(ibxrcd);
    ib_destroy_cq(xrcd->cq);
    
    ib_dealloc_pd(xrcd->pd);
    roce3_rdma_xrcd_free(rdev->hwdev, xrcd->xrcdn);
    return 0;
}
#elif defined(OFED_MLNX_5_4)
void roce3_dealloc_xrcd(struct ib_xrcd *ibxrcd, struct ib_udata *udata)
{
    struct roce3_device *rdev = NULL;
    struct roce3_xrcd *xrcd = NULL;

    if (ibxrcd == NULL) {
        pr_err("[ROCE, ERR] %s: Ibxrcd is null\n", __func__);
        return;
    }

    rdev = to_roce3_dev(ibxrcd->device);
    xrcd = to_roce3_xrcd(ibxrcd);
    ib_destroy_cq(xrcd->cq);
    
    ib_dealloc_pd(xrcd->pd);
    roce3_rdma_xrcd_free(rdev->hwdev, xrcd->xrcdn);
}
#else
int roce3_dealloc_xrcd(struct ib_xrcd *ibxrcd)
{
    int ret = 0;
    struct roce3_device *rdev = NULL;
    struct roce3_xrcd *xrcd = NULL;

    if (ibxrcd == NULL) {
        pr_err("[ROCE, ERR] %s: Ibxrcd is null\n", __func__);
        return -EINVAL;
    }

    rdev = to_roce3_dev(ibxrcd->device);
    xrcd = to_roce3_xrcd(ibxrcd);
    ret = ib_destroy_cq(xrcd->cq);
    if (ret != 0) {
        dev_err(rdev->hwdev_hdl, "[ROCE, ERR] %s: Failed to destroy cq, func_id(%hu)\n", __func__, rdev->glb_func_id);
        return ret;
    }

    ib_dealloc_pd(xrcd->pd);

    roce3_rdma_xrcd_free(rdev->hwdev, xrcd->xrcdn);
    kfree(xrcd);

    return 0;
}
#endif